﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Final
{
    class Code_Generator
    {
        List<string> faults;

        int CODELOC;
        int STORLOC;
        int ACC;
        int FLAD1;

        Symbol_Table s_table;
        Constant_Table c_table;
        Stack<Token> stack;

        StringBuilder code;

        public Code_Generator(Symbol_Table stable, Constant_Table ctable, Stack<Token> tokens)
        {
            s_table = stable;
            c_table = ctable;
            stack = tokens;

            code = new StringBuilder();

            CODELOC = 1000;
            STORLOC = 2000;
            ACC = 0;
            FLAD1 = 0;

            faults = new List<string>();

            faults.Add("Unknown Fault");
            faults.Add("A number can’t be used on the left hand side of an assignment");
            faults.Add("A label can’t be used on the left hand side of an assignment");
            faults.Add("You can’t use a label in an arithmetic expression");
            faults.Add("In a statement of the form GO TO L, L can’t be a number. It must be a label");
            faults.Add("In GO TO L, L was previously used as a variable");
            faults.Add("You can’t use a number as a label for a statement");
            faults.Add("A label of a statement was previously used as a variable");
            faults.Add("Same label used to label two or more distinct statements");
        }

        public void EXEC1()
        {
        }

        public void EXEC2()
        {
        }

        public void EXEC3()
        {
        }

        public void EXEC4()
        {
            Token temp = stack.Peek();
            stack.Pop();

            if (temp.Type == Token_Type.LITERAL)
            {
                Token_PlaceHolder p =
                    new Token_PlaceHolder(
                        c_table.Insert((temp as Token_Literal).LITERAL));

                stack.Push(p);
            }
            else if (temp.Type == Token_Type.VARIABLE)
            {
                Token_Variable tv = temp as Token_Variable;
                Symbol s = s_table.FindName(tv.NAME);

                if (s == null) //Check if the symbol table contains the variable, if null than it doesn't exist
                {
                    s_table.Entry(S_Type.VAR, tv.NAME, STORLOC);
                    stack.Push(new Token_PlaceHolder(STORLOC));
                    STORLOC++;
                }
                else
                {
                    if (s.Type == S_Type.VAR)
                    {
                        stack.Push(new Token_PlaceHolder(s.Location));
                    }
                    else
                    {
                        throw (new SystemException(faults.ElementAt(3)));
                    }
                }
            }
            else
            {
                Console.WriteLine("Unknown Problem");
            }
        }

        public void EXEC5()
        {
            Token t = stack.Peek();
            stack.Pop();

            Token_PlaceHolder tp1 = stack.Peek() as Token_PlaceHolder;

            stack.Pop();

            Token_Operator op = stack.Peek() as Token_Operator;

            stack.Pop();

            Token_PlaceHolder tp2 = stack.Peek() as Token_PlaceHolder;
            stack.Pop();
            stack.Pop();

            if (tp1.POSITION == ACC)
            {
                switch (op.OPERATOR)
                {
                    case '*':
                        {
                            code.AppendLine(CODELOC + ": MULT " + tp2.POSITION);
                            CODELOC++;

                            break;
                        }
                    case '/':
                        {
                            code.AppendLine(CODELOC + ": STORE " + ACC);

                            CODELOC++;

                            code.AppendLine(CODELOC + ": LOAD " + tp2.POSITION);

                            CODELOC++;

                            code.AppendLine(CODELOC + ": DIV " + ACC);
                            CODELOC++;

                            ACC++;

                            break;
                        }
                    case '+':
                        {
                            code.AppendLine(CODELOC + ": ADD " + tp2.POSITION);
                            CODELOC++;

                            break;
                        }
                    case '-':
                        {
                            code.AppendLine(CODELOC + ": STORE " + ACC);

                            CODELOC++;

                            code.AppendLine(CODELOC + ": LOAD " + tp2.POSITION);

                            CODELOC++;

                            code.AppendLine(CODELOC + ": SUB " + ACC);
                            CODELOC++;

                            ACC++;

                            break;
                        }
                    default:
                        {
                            //Operator not recognized

                            break;
                        }
                }

                stack.Push(new Token_PlaceHolder(ACC));
            }
            else if (tp2.POSITION == ACC)
            {
                switch (op.OPERATOR)
                {
                    case '*':
                        {
                            code.AppendLine(CODELOC + ": MULT " + tp1.POSITION);
                            CODELOC++;

                            break;
                        }
                    case '/':
                        {
                            code.AppendLine(CODELOC + ": DIV " + tp1.POSITION);
                            CODELOC++;

                            break;
                        }
                    case '+':
                        {
                            code.AppendLine(CODELOC + ": ADD " + tp1.POSITION);
                            CODELOC++;

                            break;
                        }
                    case '-':
                        {
                            code.AppendLine(CODELOC + ": SUB " + tp1.POSITION);
                            CODELOC++;

                            break;
                        }
                    default:
                        {
                            //Operator not recognized

                            break;
                        }
                }

                stack.Push(new Token_PlaceHolder(ACC));
            }
            else
            {
                if (AccEmpty())
                {
                    ACC++;
                    stack.Push(new Token_PlaceHolder(ACC));
                }
                else
                {

                    code.AppendLine(CODELOC + ": STORE " + ACC);

                    CODELOC++;

                    ACC++;

                    stack.Push(new Token_PlaceHolder(ACC));
                }

                switch (op.OPERATOR)
                {
                    case '*':
                        {
                            Compile_Assist(tp2.POSITION, tp1.POSITION, "MUL");

                            break;
                        }
                    case '/':
                        {
                            Compile_Assist(tp2.POSITION, tp1.POSITION, "DIV");


                            break;
                        }
                    case '+':
                        {
                            Compile_Assist(tp2.POSITION, tp1.POSITION, "ADD");

                            break;
                        }
                    case '-':
                        {
                            Compile_Assist(tp2.POSITION, tp1.POSITION, "SUB");

                            break;
                        }
                    default:
                        {
                            //Operator not recognized

                            break;
                        }
                }
            }
        }

        public void EXEC6()
        {
        }

        public void EXEC7()
        {
            Token t = stack.Peek();
            stack.Pop();

            Token_PlaceHolder tp = stack.Peek() as Token_PlaceHolder;

            stack.Pop();

            Token_Operator op = stack.Peek() as Token_Operator;

            stack.Pop();

            if (stack.Peek().Type == Token_Type.VARIABLE)
            {
                Token_Variable tv = stack.Peek() as Token_Variable;

                stack.Pop();

                Symbol s = s_table.FindName(tv.NAME);

                if (s == null) //Check if the symbol table contains the variable, if null than it doesn't exist
                {
                    s_table.Entry(S_Type.VAR, tv.NAME, STORLOC);
                    stack.Push(new Token_PlaceHolder(STORLOC));
                    STORLOC++;
                }
                else
                {
                    if (s.Type == S_Type.VAR)
                    {
                        stack.Push(new Token_PlaceHolder(s.Location));
                    }
                    else
                    {
                        //Print Fault 3
                    }
                }

                //if ( ACC > 1 )
                //{
                //    string temp1 = CODELOC + ": " + "LOAD " + tp.POSITION;

                //    CODELOC++;

                //    code.AppendLine(temp1);
                //}

                string temp2 = CODELOC + ": " + "STORE " + (stack.Peek() as Token_PlaceHolder).POSITION;

                CODELOC++;

                code.AppendLine(temp2);

                stack.Pop();

                stack.Push(new Token_Special(Special_Type.S));

                stack.Push(t);
            }
            else
            {
                throw (new SystemException(faults.ElementAt(1)));
            }
        }

        public void EXEC8()
        {
        }

        public void EXEC9()
        {
            string temp = CODELOC + ": " + "HALT";

            CODELOC++;

            code.AppendLine(temp);

            PrintCode();

            Console.WriteLine();

            s_table.Print();

            Console.WriteLine();

            c_table.Print();
        }

        public void PrintCode()
        {
            Console.Write(code.ToString());


        }

        public bool AccEmpty()
        {
            if (ACC == 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public void Compile_Assist(int pos1, int pos2, string operation)
        {
            string temp = CODELOC + ": LOAD " + pos1;

            CODELOC++;

            code.AppendLine(temp);

            temp = CODELOC + ": " + operation +  " " + pos2;

            CODELOC++;

            code.AppendLine(temp);
        }
    }
}
